<?php
/**
 ***********************************************************************************************************************
 * MySQL驱动类
 ***********************************************************************************************************************
 */
namespace artisan\db;

use mysqli;
use mysqli_result;
use mysqli_sql_exception;
use artisan\http;
use artisan\log;


/**
 * Class mysqlDriver
 * @package artisan\db
 */
class mysqlDriver
{
    /**
     * Static instance of self
     *
     * @var mysqlDriver
     */
    protected static $_instance;
    /**
     * Table prefix
     *
     * @var string
     */
    protected static $_prefix;
    /**
     * MySQLi instance
     *
     * @var \mysqli
     */
    protected $_mysqli;
    /**
     * The SQL query to be prepared and executed
     *
     * @var string
     */
    protected $_query;

    /**
     * @var
     */
    protected $_resource;

    /**
     * @var bool
     */
    protected $_transaction_in_progress = true;

    /**
     * Database credentials
     *
     * @var string
     */
    protected $host;
    /**
     * @var null|string
     */
    protected $username;
    /**
     * @var null|string
     */
    protected $password;
    /**
     * @var null|string
     */
    protected $db;
    /**
     * @var int|null
     */
    protected $port;
    /**
     * @var string
     */
    protected $charset;

    /**唯一id
     * @var string
     */
    protected $uuid = '';

    /**
     * @var array
     */
    private static $sqlConfigs = [
//        'SET interactive_timeout = 20',
        'SET wait_timeout = 20',//mysql自动关闭的最大空闲时间
    ];

    /**
     * 是否启用连接池
     */
    const ENABLE_CONNECTION_POOL = true;

    /**
     * 初始化配置文件
     * @param string $host
     * @param string $username
     * @param string $password
     * @param string $db
     * @param int $port
     */
    public function __construct($host = NULL, $username = NULL, $password = NULL, $db = NULL, $port = NULL, $charset = 'utf8')
    {
        $this->uuid = uniqid('mysql', true);
        $this->host = $host;
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
        $this->port = $port;
        $this->charset = $charset;
        $this->connect();
        self::ENABLE_CONNECTION_POOL && $this->preConfig();
        self::$_instance = $this;
    }

    /**
     * 连接数据库
     */
    public function connect()
    {
        if(!$this->host) {
            logCritical('Mysql host is not set !', '', 'mysql');
            die('Mysql host is not set !');
        }
        // 打开mysql错误报告 by Ljp
        //@TODO 可以抛出异常,后续看看是否有其他方案
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        try{
            $this->_mysqli = new \mysqli(
                $this->host,
                $this->username,
                $this->password,
                $this->db,
                $this->port
            );

            //每次connect都进行utf8设置，浪费效率，不如直接修改my.cnf配置文件
            if($this->_mysqli && $this->charset) {
                $this->_mysqli->set_charset($this->charset);
            }

            //开始记录连接数
            DBMonitorUtil::monitorConnectCountsStart($this->_mysqli,$this->host,$this->username,$this->db,$this->uuid);

        } catch (mysqli_sql_exception $e) {
            logCritical('Mysqli connect fail ! ['.$e->getMessage().'] ['.$e->getCode().']', $this->host.':'.$this->db, 'mysql');
            die('Mysqli connect fail !');
        }
    }

    /**
     * 返回当前操作实例
     * @return object Returns the current instance.
     */
    public static function getInstance()
    {
        return self::$_instance;
    }

    /**
     * @return array
     */
    private function preConfig()
    {
        return is_array(self::$sqlConfigs) && self::$sqlConfigs ? array_map(function($sql) {
            $this->query($sql);
        }, self::$sqlConfigs) : [];
    }

    /**
     *
     * @param string $query   Contains a user-provided select query.
     * @param int    $numRows The number of rows total to return.
     *
     * @return mixed Contains the returned rows from the query.
     */
    public function query($query)
    {
        //全局拦截不需要执行的sql
        if(!DBMonitorUtil::checkSqlIsOk($this->host,$query)){
            return false;
        }

        DBMonitorUtil::setToRedis($query);      //将获取的表名放入redis
        //判断连接是否已经断开
        if(!$this->_mysqli instanceof mysqli || !$this->ping()){
            $this->connect();
//            /**********重连检测*****start*****/
//            $hasGoneAway = $this->hasGoneAway();
//            if($hasGoneAway){
//                logDebug('新框架query:连接断开,重试重连:'.$this->db,'db_recon');
//                $this->connect();//重连
//            }
//            /**********重连检测******end****/
        }

        $this->_query = $query;

        if(empty($query)) {
            logError('Mysqli query fail ! [empty sql given]', $this->host.':'.$this->db, 'mysql');
            $this->logTrace();
            //logDebug('Debug trace ~ File:[' . $this->logTrace()['file'] . '] Line:[' . $this->logTrace()['line'] . ']', 'trace');
            return false;
        }

        try{
            $result = $this->_mysqli->query($query);
            if(!self::ENABLE_CONNECTION_POOL){
                $this->_mysqli->close();
            }
            return $result;
        } catch(mysqli_sql_exception $e) {
            // 唯一索引导致的执行失败不记录
            if(preg_match('/Duplicate\s*entry/', (string)$e->getMessage())) {
                return false;
            }
            //判断sql是不是过长
            if(strpos($e->getMessage(), '_bkp_') === false && strlen($query) > 700 && strpos($query,' api_log ') === false){
                //记录超长度的sql错误
                http::post('http://api.kuaidihelp.com/v1/MyTest/remoteLogFile',['message'=>['sql_err'=>$query,'err_msg'=>$e->getMessage(),'err_code'=>$e->getCode()]],['TIMEOUT'=>1]);
            }
            logError('Mysqli query fail ! ['.$query.']', $this->host.':'.$this->db, 'mysql');
            logError('[Error]:' . $e->getMessage() . ' [Code]:' . $e->getCode(), $this->host.':'.$this->db, 'mysql');
            return false;
        }
    }

    /**
     * 返回上次写入记录ID
     *
     */
    public function getInsertId()
    {
        return $this->_mysqli->insert_id;
    }

    /**
     * 返回上次操作影响行数
     *
     */
    public function getAffectedRows()
    {
        return $this->_mysqli->affected_rows;
    }

    /**
     * 过滤非法字符格式
     *
     */
    public function escape($str)
    {
        return $this->_mysqli->real_escape_string($str);
    }

    /**
     * 检测连接状态,如断开则尝试重连(需要配合libmysqlclient驱动并且mysqli.reconnect启用). 
     * 尝试使用try catch重连(兼容mysqlnd)
     */
    public function ping()
    {
        static $timeStamp = null;
        if(self::ENABLE_CONNECTION_POOL){
            if($timeStamp === null){
                $timeStamp = microtime(true);
                return true;
            }
            if (($tmpTime = microtime(true)) - $timeStamp < 1){
                return true;
            }
            $timeStamp = $tmpTime;
        }
        $ret = false;
        try{
            $ret = $this->_mysqli->ping();
        } catch (mysqli_sql_exception $e){
            if ($e->getCode() === 2006) {
                $this->connect();
                $ret = $this->_mysqli->ping();
            }
        }
        return $ret;
    }

    /**
     * 判断mysql连接是否已被关闭
     *
     */
    public function hasGoneAway()
    {
        $mysqlGoneAway=false;
        try{
            $this->_mysqli->query('SELECT LAST_INSERT_ID()');
        }catch (mysqli_sql_exception $e){
            if(2006 === $e->getCode()){
                $mysqlGoneAway = true;
            }
        }
        return $mysqlGoneAway ?: (2006 === mysqli_errno($this->_mysqli));
    }

    /**
     * 解析数据集结果
     *
     */
    public function fetchArray($resource = null)
    {
        $result = [];
        if($resource instanceof mysqli_result){
            if(function_exists('mysqli_fetch_all')){
                $result = $resource->fetch_all(MYSQLI_ASSOC);
            }else{
                while ($row = mysqli_fetch_assoc($resource)) {
                    $result[] = $row;
                }
            }
        }
        return $result;
    }


    /**
     * 关闭连接
     */
    public function __destruct()
    {
        $this->close();
    }

    /**
     * 关闭当前连接
     */
    public function close()
    {
        if($this->_mysqli) {
            //结束记录连接数
            DBMonitorUtil::monitorConnectCountsEnd($this->_mysqli,$this->host,$this->db,$this->uuid);

            if(!$isOk = $this->_mysqli->close()){
                logDebug('警告:mysql close出错，可能存在多次关闭的情况','msg_cons_err');
            }
            $this->_mysqli = null;//重置为null,不然下次connect判断_mysqli有值就不会重新connect了
            return $isOk;
        }
        return false;
    }

    /**
     * 获取上次执行错误
     *
     * @return string
     */
    public function getLastError()
    {
        return $this->_mysqli->error;
    }

    /**
     * Begin a transaction
     *
     * @uses mysqli->autocommit(false)
     * @uses register_shutdown_function(array($this, "_transaction_shutdown_check"))
     */
    public function startTransaction()
    {
        $this->_mysqli->autocommit (false);
        $this->_transaction_in_progress = true;
        register_shutdown_function ([$this, '_transaction_status_check']);
    }

    /**
     * Transaction commit
     *
     * @uses mysqli->commit();
     * @uses mysqli->autocommit(true);
     */
    public function commit()
    {
        $this->_mysqli->commit();
        $this->_transaction_in_progress = false;
        $this->_mysqli->autocommit(true);
    }

    /**
     * Transaction rollback function
     *
     * @uses mysqli->rollback();
     * @uses mysqli->autocommit(true);
     */
    public function rollback()
    {
        $this->_mysqli->rollback();
        $this->_transaction_in_progress = false;
        $this->_mysqli->autocommit(true);
    }

    /**
     * Shutdown handler to rollback uncommited operations in order to keep
     * atomic operations sane.
     *
     * @uses mysqli->rollback();
     */
    public function _transaction_status_check()
    {
        if(!$this->_transaction_in_progress) {
            return;
        }

        $this->rollback();
    }

    /**
     * 格式回溯信息
     *
     * @return array
     */
    protected function logTrace()
    {
        logDebug(debug_backtrace(false, false));
//        foreach((array)debug_backtrace(false, false) as $key => $val) {
//            if(
//                !empty($val['class']) &&
//                !empty($val['function']) &&
//                ($val['class'] == 'artisan\db\mysqlDriver')
//            ) {
//                return [
//                    'file' => !empty($val['file']) ? $val['file'] : '',
//                    'line' => !empty($val['line']) ? $val['line'] : '',
//                ];
//            }
//        }
//        return [
//            'file' => '',
//            'line' => ''
//        ];
    }

}
